package de.tsajar.es.networking.client;

import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Client;
import com.jme3.network.serializing.Serializer;
import com.simsilica.es.ComponentFilter;
import com.simsilica.es.Entity;
import com.simsilica.es.EntityComponent;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;
import com.simsilica.es.EntitySet;
import com.simsilica.es.StringIndex;
import com.simsilica.es.filter.AndFilter;
import com.simsilica.es.filter.FieldFilter;
import com.simsilica.es.filter.OrFilter;
import de.tsajar.es.networking.ReadOnlyEntity;
import de.tsajar.es.networking.messages.EntityChangeMessage;
import de.tsajar.es.networking.messages.RequestEntitySetMessage;
import de.tsajar.es.networking.serializer.ClassSerializer;
import de.tsajar.es.networking.messages.EntitySetResponseMessage;
import de.tsajar.es.networking.serializer.EntitySystemSerializer;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Gives access to a remote EntityData with attached EntityDataServer
 *
 * @author Eike Foede
 */
public class RemoteEntityData implements EntityData, MessageListener<Client> {

    Client conn;
    private long requestCounter = 0;
    //EntitySets which are not yet accepted by server
    private Map<Long, RemoteEntitySet> requestedEntitySets = new ConcurrentHashMap<Long, RemoteEntitySet>();
    //Accepted EntitySets
    private Map<Long, RemoteEntitySet> remoteEntitySets = new ConcurrentHashMap<Long, RemoteEntitySet>();

    public RemoteEntityData(Client conn) {
        this.conn = conn;
        //Setup networking
        Serializer.registerClasses(EntityChangeMessage.class,
                EntitySetResponseMessage.class,
                RequestEntitySetMessage.class);
        Serializer s = new EntitySystemSerializer();
        Serializer.registerClass(Entity.class, s);
        Serializer.registerClass(FieldFilter.class, s);
        Serializer.registerClass(OrFilter.class, s);
        Serializer.registerClass(AndFilter.class, s);
        Serializer.registerClass(Class.class, new ClassSerializer());
        conn.addMessageListener(this, EntityChangeMessage.class, EntitySetResponseMessage.class);

    }

    @Override
    public EntityId createEntity() {
        throw new UnsupportedOperationException("Readonly ES!");
    }

    @Override
    public void removeEntity(EntityId entityId) {
        throw new UnsupportedOperationException("Readonly ES!");
    }

    @Override
    public void setComponent(EntityId entityId, EntityComponent component) {
        throw new UnsupportedOperationException("Readonly ES!");
    }

    @Override
    public void setComponents(EntityId entityId, EntityComponent... components) {
        throw new UnsupportedOperationException("Readonly ES!");
    }

    @Override
    public boolean removeComponent(EntityId entityId, Class type) {
        throw new UnsupportedOperationException("Readonly ES!");
    }

    @Override
    public <T extends EntityComponent> T getComponent(EntityId entityId, Class<T> type) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public Entity getEntity(EntityId entityId, Class... types) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public EntityId findEntity(ComponentFilter filter, Class... types) {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public Set<EntityId> findEntities(ComponentFilter filter, Class... types) {
        throw new UnsupportedOperationException("Not supported.");
    }

    /**
     * Get EntitySet with entities from server
     *
     * @param types
     * @return
     */
    @Override
    public EntitySet getEntities(Class... types) {
        //Create EntitySet
        RemoteEntitySet res = new RemoteEntitySet();
        requestedEntitySets.put(requestCounter, res);
        //Send request to server
        RequestEntitySetMessage rem = new RequestEntitySetMessage();
        rem.requestId = requestCounter++;
        rem.f = null;
        rem.types = types;
        conn.send(rem);
        return res;
    }

    /**
     * Get EntitySet with entities from server.
     *
     * @param filter
     * @param types
     * @return
     */
    @Override
    public EntitySet getEntities(ComponentFilter filter, Class... types) {
        //Create EntitySet
        RemoteEntitySet res = new RemoteEntitySet();
        requestedEntitySets.put(requestCounter, res);
        //Send request to server
        RequestEntitySetMessage rem = new RequestEntitySetMessage();
        rem.requestId = requestCounter++;
        rem.f = filter;
        rem.types = types;
        conn.send(rem);
        return res;
    }

    @Override
    public StringIndex getStrings() {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    public void close() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void messageReceived(Client s, Message msg) {
        if (msg instanceof EntitySetResponseMessage) {//EntitySet got accepted
            EntitySetResponseMessage esrm = (EntitySetResponseMessage) msg;
            //Move EntitySet from requested to approved EntitySets
            RemoteEntitySet res = requestedEntitySets.remove(esrm.requestId);
            remoteEntitySets.put(esrm.entitySetId, res);

        } else if (msg instanceof EntityChangeMessage) {//EntitySet changed

            EntityChangeMessage ecm = (EntityChangeMessage) msg;
            RemoteEntitySet res = remoteEntitySets.get(ecm.getId());
            //Apply changes
            for (Entity e : ecm.getChanged()) {
                ((ReadOnlyEntity) e).setLastUpdated(ecm.getTime());
            }
            for (Entity e : ecm.getAdded()) {
                ((ReadOnlyEntity) e).setLastUpdated(ecm.getTime());
            }
            res.addToAdd(ecm.getAdded());
            res.addToChange(ecm.getChanged());
            res.addToRemove(ecm.getRemoved());
        }
    }
}
